#![allow(unused)]

use core::{alloc::Layout, marker::PhantomData, mem::size_of, ptr};

use embedded_dma::{ReadBuffer, Word};

use crate::{alloc_coherent, dealloc_coherent, BusAddr, DMAInfo};

pub struct BoxCoherent<T> {
    info: DMAInfo,
    size: usize,
    _marker: PhantomData<T>,
}
impl<T> BoxCoherent<T> {
    #[inline(always)]
    pub fn as_ptr(&self) -> *const T {
        self.info.cpu_addr as *const T
    }
    #[inline(always)]
    pub fn as_mut_ptr(&mut self) -> *mut T {
        self.info.cpu_addr as *mut T
    }

    pub fn bus_addr(&self) -> BusAddr {
        BusAddr::new(self.info.bus_addr)
    }
}
impl<T: Sized> BoxCoherent<T> {
    pub fn new(value: T) -> Option<Self> {
        unsafe {
            let size = size_of::<T>();
            let layout = Layout::from_size_align(size, size).unwrap();
            let info = alloc_coherent(layout)?;
            core::ptr::write(info.cpu_addr as *mut T, value);

            Some(Self {
                info,
                size,
                _marker: PhantomData,
            })
        }
    }

    #[inline(always)]
    pub fn read_volatile(&self) -> T {
        unsafe { core::ptr::read_volatile(self.as_ptr()) }
    }

    #[inline(always)]
    pub fn write_volatile(&mut self, val: T) {
        unsafe {
            core::ptr::write_volatile(self.as_mut_ptr(), val);
        }
    }
}

impl<T> Drop for BoxCoherent<T> {
    fn drop(&mut self) {
        unsafe {
            ptr::drop_in_place(self.as_mut_ptr());
            let layout = Layout::from_size_align(self.size, self.size).unwrap();
            dealloc_coherent(self.info, layout);
        }
    }
}

#[cfg(test)]
mod test {
    use core::{
        alloc::Layout,
        sync::atomic::{AtomicBool, Ordering},
    };

    use alloc::sync::Arc;

    use crate::{array::ArrayCoherent, set_impl, DMAInfo, Impl};

    use super::BoxCoherent;

    struct A {}
    unsafe impl Impl for A {
        unsafe fn alloc_coherent(layout: Layout) -> Option<crate::DMAInfo> {
            let ptr = alloc::alloc::alloc(layout);
            if ptr.is_null() {
                return None;
            }

            Some(DMAInfo {
                cpu_addr: ptr as usize,
                bus_addr: ptr as usize as u64,
            })
        }

        unsafe fn dealloc_coherent(dma: DMAInfo, layout: Layout) {
            alloc::alloc::dealloc(dma.cpu_addr as *mut u8, layout)
        }
    }

    set_impl!(A);

    struct Test {
        a: Arc<AtomicBool>,
    }

    impl Drop for Test {
        fn drop(&mut self) {
            let a = self
                .a
                .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |f| Some(true))
                .unwrap();

            if a == true {
                panic!("drop twice");
            }
        }
    }

    #[test]
    fn test_dma_buffer() {
        let a = Arc::new(AtomicBool::new(false));

        unsafe {
            let dma = BoxCoherent::new(Test { a: a.clone() }).unwrap();
            dma.read_volatile();
            drop(dma);
        }

        let v = ArrayCoherent::<u64>::zero(10, 4096).unwrap();
        assert_eq!(v.len(), 10);
    }
}
